package com.symaster.common.test.animation;

import com.symaster.common.animation.Animation;
import com.symaster.common.animation.Curve;
import com.symaster.common.geom.entity.Vector;
import com.symaster.common.geom.util.PointUtil;
import com.symaster.common.swing.util.SwingUtil;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import static com.symaster.common.test.animation.AnimationTestObj.Box;

/**
 * @author yinmiao
 * @version 2023/11/7
 */
public class AnimationTest2 {
    public static void main(String[] args) {

        final Curve curve = Curve.ofJson("{\"curvePoints\":[{\"x\":0.0,\"y\":1.0},{\"x\":0.3,\"y\":1.0},{\"x\":0.5,\"y\":0.5},{\"x\":0.7,\"y\":0.0},{\"x\":1.0,\"y\":0.0}]}");
        // final Curve curve = Curve.TYPE_BALANCED;
        final Animation animation = new Animation();

        JPanel jPanel = new JPanel();
        jPanel.setLayout(new BorderLayout());

        Canvas canvas = new Canvas(curve, animation);
        for (int i = 0; i < 10; i++) {
            Box box = new Box();
            box.x = 20.0;
            box.y = 20.0;
            box.width = 50.0;
            box.height = 50.0;

            canvas.addBox(box);
        }

        jPanel.add(canvas);

        JPanel jPanel1 = new JPanel();
        jPanel1.setLayout(new FlowLayout());

        JButton startButton = new JButton("Exec");
        JButton stopButton = new JButton("Stop");
        jPanel1.add(startButton);
        jPanel1.add(stopButton);

        startButton.addActionListener(new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent e) {
                canvas.start();
                stopButton.setEnabled(true);
                startButton.setEnabled(false);
            }
        });
        stopButton.setEnabled(false);
        stopButton.addActionListener(new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent e) {
                canvas.stop();
                stopButton.setEnabled(false);
                startButton.setEnabled(true);
            }
        });

        jPanel.add(jPanel1, BorderLayout.EAST);

        JFrame jFrame = new JFrame("AnimationTest");
        jFrame.setContentPane(jPanel);
        jFrame.setPreferredSize(new Dimension(900, 600));
        jFrame.pack();
        SwingUtil.toCenter(jFrame);
        jFrame.setVisible(true);
    }

    public static class Canvas extends JComponent {
        public static final ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1, 1, 3 * 1000, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(), new ThreadPoolExecutor.DiscardOldestPolicy());
        private final Curve curve;
        private final Animation animation;
        private final List<Box> boxList = new ArrayList<>();
        private final List<Config> configList = new ArrayList<>();
        private boolean run = false;
        private final Random random = new Random();
        private Double lastTime;
        public final double[] times = new double[120];
        public int maxFps = 120;

        public Canvas(Curve curve, Animation animation) {
            this.curve = curve;
            this.animation = animation;
            Arrays.fill(times, -1.0);
        }

        @Override
        public void paint(Graphics g) {
            if (!run) {
                Arrays.fill(times, -1.0);
                lastTime = null;
                return;
            }
            double thatTime = System.nanoTime() / 1000000.0;

            for (int i = 0; i < boxList.size(); i++) {
                Box box = boxList.get(i);
                Config config = configList.get(i);
                if (config.startTime == null) {
                    init(config, box);
                }
                double v = thatTime - config.startTime;
                if (v > config.executionTime) {
                    init(config, box);
                }

                double scale = animation.getPoint(curve, (System.nanoTime() / 1000000.0 - config.startTime) / config.executionTime);
                box.x = config.srcX + config.vector.getX() * scale;
                box.y = config.srcY + config.vector.getY() * scale;
                drawBox(g, box);
            }

            double blankingTime = 0;
            if (lastTime != null) {
                blankingTime = thatTime - lastTime;
            }
            final double finalBlankingTime = blankingTime;

            System.arraycopy(times, 1, times, 0, times.length - 1);
            times[times.length - 1] = System.nanoTime() / 1000000.0;
            if (times[0] > 0) {
                drawFps(g);
            }

            threadPoolExecutor.execute(() -> {
                try {
                    // 秒 / 最大帧数限制 - 当前毫秒值 - 总耗时(上次绘制结束时间至当前时间) = 下一帧需要等待的时间
                    double v2 = (1000.0 / maxFps) - (System.nanoTime() / 1000000.0 - thatTime + finalBlankingTime); // 剩余需要等待时间

                    if (v2 > 0) {
                        // 转换成纳秒等待, 这样精度较高
                        long round = Math.round(v2 * 1000000.0);
                        TimeUnit.NANOSECONDS.sleep(round);
                        // TimeUnit.MICROSECONDS.sleep(round);
                    }
                    lastTime = System.nanoTime() / 1000000.0;
                    repaint();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            });
        }

        private void drawFps(Graphics g) {
            Graphics graphics = g.create(0, 0, 100, 30);

            try {
                SwingUtil.antialiasingOn((Graphics2D) graphics);

                double consume = (times[times.length - 1] - times[0]) / times.length; // 表示渲染一帧所耗时

                double v1 = 1000 / consume;

                graphics.setColor(new Color(0, 186, 21));
                graphics.drawString(String.format("Fps:%s", Math.round(v1)), 3, 15);
            } finally {
                graphics.dispose();
            }
        }

        private void drawBox(Graphics g, Box box) {

            double x = box.x;
            double y = box.y;

            Graphics graphics = g.create((int) Math.round(box.x - 1), (int) Math.round(box.y - 1), (int) Math.round(box.width + 2), (int) Math.round(box.height + 2));
            SwingUtil.antialiasingOn((Graphics2D) graphics);

            try {

                // if (box.lastX != null && box.lastY != null) {
                //     drawLastBox(g, box);
                // }

                graphics.setColor(new Color(0, 128, 255));
                graphics.fillOval(0, 0, (int) Math.round(box.width + 2), (int) Math.round(box.height + 2));
                // g.fillRect(box.x - 1, box.y - 1, box.width + 2, box.height + 2);

                graphics.setColor(new Color(26, 26, 26));
                graphics.fillOval(1, 1, (int) Math.round(box.width), (int) Math.round(box.height));

                box.lastX = x;
                box.lastY = y;
                // g.fillRect(box.x, box.y, box.width, box.height);
            } finally {
                graphics.dispose();
            }
        }

        private void drawLastBox(Graphics g, Box box) {
            Graphics graphics = g.create((int) Math.round(box.lastX - 1), (int) Math.round(box.lastY - 1), (int) Math.round(box.width + 2), (int) Math.round(box.height + 2));

            try {
                graphics.setColor(new Color(0, 128, 255, 150));
                graphics.fillOval(0, 0, (int) Math.round(box.width + 2), (int) Math.round(box.height + 2));

                graphics.setColor(new Color(26, 26, 26, 150));
                graphics.fillOval(1, 1, (int) Math.round(box.width), (int) Math.round(box.height));
            } finally {
                graphics.dispose();
            }
        }

        private void init(Config config, Box box) {
            config.startTime = System.nanoTime() / 1000000.0;
            config.srcX = box.x;
            config.srcY = box.y;
            double dstX = random.nextDouble() * (getWidth() - box.width);
            double dstY = random.nextDouble() * (getHeight() - box.height);

            config.vector = PointUtil.getTranslateVector(config.srcX, config.srcY, dstX, dstY);
            config.executionTime = config.vector.length();
        }

        public void addBox(Box box) {
            boxList.add(box);
            configList.add(new Config());
        }

        public void start() {
            run = true;
            repaint();
        }

        public void stop() {
            run = false;
        }
    }

    public static class Config {
        public Double startTime; // ms
        public Double executionTime; // ms
        public Vector vector;

        public double srcX;
        public double srcY;
    }
}
